package cn.miju.admin.framework.aspect;

import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTPayload;
import cn.hutool.jwt.JWTUtil;
import cn.miju.admin.framework.annotation.NoToken;
import cn.miju.admin.framework.constant.AdminConstant;
import cn.miju.admin.framework.exception.ServiceException;
import cn.miju.database.entity.SysOperLog;
import cn.miju.database.entity.SysUser;
import cn.miju.database.mapper.SysOperLogMapper;
import com.alibaba.fastjson2.JSONObject;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

/**
 * <pre>
 * AOP
 * </pre>
 *
 * @author zhuming
 * @version 1.0.0
 * @since 2023-04-19 17:16
 */
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class AdminAspect {
    private final SysOperLogMapper sysOperLogMapper;

    @SneakyThrows
    @Around("execution(public * cn.miju.admin.*.controller.*.*(..))")
    public Object arround(ProceedingJoinPoint joinPoint) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String authorization = request.getHeader(AdminConstant.TOKEN);
        Object[] arguments = joinPoint.getArgs();
        log.info(Arrays.stream(arguments).collect(Collectors.toList()).toString());
        Object body = arguments.length > 0 ? arguments[0] : null;
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        if (null == method.getAnnotation(NoToken.class)) {
            if (StringUtils.isBlank(authorization)) {
                throw new ServiceException(HttpStatus.UNAUTHORIZED);
            }
            JWT jwt = null;
            try {
                jwt = JWTUtil.parseToken(authorization);
            } catch (Exception e) {
                throw new ServiceException(HttpStatus.UNAUTHORIZED);
            }
            if (!jwt.setKey(AdminConstant.JWT.PRIVATE_KEY.getBytes(StandardCharsets.UTF_8)).validate(0)) {
                throw new ServiceException(HttpStatus.UNAUTHORIZED);
            }
            JWTPayload payload = jwt.getPayload();
            SysUser sysUser = JSONObject.parseObject(JSONObject.toJSONString(payload.getClaim(AdminConstant.JWT.USER_INFO_KEY)), SysUser.class);
            if (Objects.nonNull(body)) {
                Field userField = body.getClass().getSuperclass().getDeclaredField("loginUser");
                userField.setAccessible(true);
                userField.set(body, sysUser);
                userField.setAccessible(false);
            }
            if (!Objects.equals(request.getRequestURI(), "/system/operLog/page")) {
                this.saveOperLog(request, method.getAnnotation(ApiOperation.class), sysUser.getId());
            }
        }
        return joinPoint.proceed();
    }

    /**
     * 保存操作日志
     * @param request 请求对象
     * @param apiOperation swagger注解
     * @param userId 用户ID
     */
    private void saveOperLog(HttpServletRequest request, ApiOperation apiOperation, Long userId) {
        CompletableFuture.runAsync(() -> sysOperLogMapper.insert(SysOperLog.builder().
                operDate(LocalDate.now()).
                userId(userId).
                bizName(apiOperation.value()).
                ip(ServletUtil.getClientIP(request)).
                build()));
    }
}
